/*
 * LvAnim.h
 *
 */

#ifndef LVANIM_H_
#define LVANIM_H_

#include "../core/lvglpp.h"

namespace lvglpp
{

	class LvAnim : protected lv_anim_t
	{
		static void _deleter(lv_anim_t *a) noexcept { lv_anim_del(a->var, a->exec_cb); }

	public:
		using handle_t = LvPointer<lv_anim_t, _deleter>;

	protected:
		/**
		 * Create an animation
		 * @return          handle to the created animation (different from the `a` parameter)
		 */
		handle_t start() const noexcept { return handle_t{lv_anim_start(this)}; }

		/**
		 * Set a variable to animate
		 * @param var   pointer to a variable to animate
		 */
		inline void setVar(void *var) noexcept { return lv_anim_set_var(this, var); }

		/**
		 * Set a function to animate `var`
		 * @param exec_cb   a function to execute during animation
		 *                  LVGL's built-in functions can be used.
		 *                  E.g. lv_obj_set_x
		 */
		inline void setExecCb(lv_anim_exec_xcb_t exec_cb) noexcept { return lv_anim_set_exec_cb(this, exec_cb); }

		/**
		 * Similar to `lv_anim_set_exec_cb` but `lv_anim_custom_exec_cb_t` receives
		 * `lv_anim_t * ` as its first parameter instead of `void *`.
		 * This function might be used when LVGL is bound to other languages because
		 * it's more consistent to have `lv_anim_t *` as first parameter.
		 * The variable to animate can be stored in the animation's `user_data`
		 * @param exec_cb   a function to execute.
		 */
		inline void setExecCb(lv_anim_custom_exec_cb_t exec_cb) noexcept { return lv_anim_set_custom_exec_cb(this, exec_cb); }

	public:
		/**
		 * Initialize an animation variable.
		 * E.g.:
		 * LvAnim a;
		 * a.set_...;
		 * auto b = a.start();
		 */
		inline LvAnim() noexcept { lv_anim_init(this); }

		inline lv_anim_t *raw() noexcept { return this; }

		template <typename T>
		requires std::is_lvalue_reference_v<T> || std::is_pointer_v<T>
		inline handle_t start(void (*func)(T, int32_t), std::type_identity_t<T> var) noexcept
		{
			if constexpr (std::is_lvalue_reference_v<T>)
				setVar(&var);
			else
				setVar(var);
			setExecCb((lv_anim_exec_xcb_t)func);
			return start();
		}

		template <std::invocable<int32_t> T>
		inline handle_t start(T &&func) noexcept requires(sizeof(T) <= sizeof(void *) && std::is_trivially_destructible_v<T> && std::is_trivially_move_constructible_v<T>)
		{
			new (&this->var) T{std::forward<T>(func)};
			setExecCb([](void *var, int32_t v)
					  { ((T &)var)(v); });
			return start();
		}

		inline handle_t start(auto &func) = delete;

		/**
		 * Set the duration of an animation
		 * @param duration  duration of the animation in milliseconds
		 */
		inline void setTime(uint32_t duration) noexcept { return lv_anim_set_time(this, duration); }
		/**
		 * Set a delay before starting the animation
		 * @param delay     delay before the animation in milliseconds
		 */
		inline void setDelay(uint32_t delay) noexcept { return lv_anim_set_delay(this, delay); }

		/**
		 * Set the start and end values of an animation
		 * @param start     the start value
		 * @param end       the end value
		 */
		inline void setValues(int32_t start, int32_t end) noexcept { return lv_anim_set_values(this, start, end); }

		/**
		 * Set the path (curve) of the animation.
		 * @param path_cb a function to set the current value of the animation.
		 */
		inline void setPathCb(lv_anim_path_cb_t path_cb) noexcept { return lv_anim_set_path_cb(this, path_cb); }

		/**
		 * Set a function call when the animation really starts (considering `delay`)
		 * @param start_cb  a function call when the animation starts
		 */
		inline void setStartCb(lv_anim_start_cb_t start_cb) noexcept { return lv_anim_set_start_cb(this, start_cb); }

		/**
		 * Set a function to use the current value of the variable and make start and end value
		 * relative the the returned current value.
		 * @param get_value_cb  a function call when the animation starts
		 */
		inline void setGetValueCb(lv_anim_get_value_cb_t get_value_cb) noexcept { return lv_anim_set_get_value_cb(this, get_value_cb); }

		/**
		 * Set a function call when the animation is ready
		 * @param ready_cb  a function call when the animation is ready
		 */
		inline void setReadyCb(lv_anim_ready_cb_t ready_cb) noexcept { return lv_anim_set_ready_cb(this, ready_cb); }

		/**
		 * Make the animation to play back to when the forward direction is ready
		 * @param time      the duration of the playback animation in milliseconds. 0: disable playback
		 */
		inline void setPlaybackTime(uint32_t time) noexcept { return lv_anim_set_playback_time(this, time); }

		/**
		 * Make the animation to play back to when the forward direction is ready
		 * @param delay     delay in milliseconds before starting the playback animation.
		 */
		inline void setPlaybackDelay(uint32_t delay) noexcept { return lv_anim_set_playback_delay(this, delay); }

		/**
		 * Make the animation repeat itself.
		 * @param cnt       repeat count or `LV_ANIM_REPEAT_INFINITE` for infinite repetition. 0: to disable repetition.
		 */
		inline void setRepeatCount(uint16_t cnt) noexcept { return lv_anim_set_repeat_count(this, cnt); }

		inline void setRepeatInfinite() noexcept { return lv_anim_set_repeat_count(this, LV_ANIM_REPEAT_INFINITE); }

		/**
		 * Set a delay before repeating the animation.
		 * @param delay     delay in milliseconds before repeating the animation.
		 */
		inline void setRepeatDelay(uint32_t delay) noexcept { return lv_anim_set_repeat_delay(this, delay); }

		/**
		 * Set a whether the animation's should be applied immediately or only when the delay expired.
		 * @param en        true: apply the start value immediately in `lv_anim_start`;
		 *                  false: apply the start value only when `delay` ms is elapsed and the animations really starts
		 */
		inline void setEarlyApply(bool en) noexcept { return lv_anim_set_early_apply(this, en); }

		/**
		 * Get a delay before starting the animation
		 * @return delay before the animation in milliseconds
		 */
		inline uint32_t getDelay() noexcept { return lv_anim_get_delay(this); }

		/**
		 * Get the time used to play the animation.
		 * @return the play time in milliseconds.
		 */
		uint32_t getPlaytime() noexcept { return lv_anim_get_playtime(this); }

		/**
		 * Calculate the current value of an animation applying linear characteristic
		 * @return      the current value to set
		 */
		void setPathLinear() noexcept { setPathCb(lv_anim_path_linear); }

		/**
		 * Calculate the current value of an animation slowing down the start phase
		 * @return      the current value to set
		 */
		void setPathEaseIn() noexcept { setPathCb(lv_anim_path_ease_in); }

		/**
		 * Calculate the current value of an animation slowing down the end phase
		 * @return      the current value to set
		 */
		void setPathEaseOut() noexcept { setPathCb(lv_anim_path_ease_out); }

		/**
		 * Calculate the current value of an animation applying an "S" characteristic (cosine)
		 * @return      the current value to set
		 */
		void setPathEaseInOut() noexcept { setPathCb(lv_anim_path_ease_in_out); }

		/**
		 * Calculate the current value of an animation with overshoot at the end
		 * @return      the current value to set
		 */
		void setPathOvershoot() noexcept { setPathCb(lv_anim_path_overshoot); }

		/**
		 * Calculate the current value of an animation with 3 bounces
		 * @return      the current value to set
		 */
		void setPathBounce() noexcept { setPathCb(lv_anim_path_bounce); }

		/**
		 * Calculate the current value of an animation applying step characteristic.
		 * (Set end value on the end of the animation)
		 * @return      the current value to set
		 */
		void setPathStep() noexcept { setPathCb(lv_anim_path_step); }

	public:
		/**
		 * Get the number of currently running animations
		 * @return      the number of running animations
		 */
		static inline uint16_t countRunning(void) noexcept { return lv_anim_count_running(); }

		/**
		 * Calculate the time of an animation with a given speed and the start and end values
		 * @param speed speed of animation in unit/sec
		 * @param start     start value of the animation
		 * @param end       end value of the animation
		 * @return          the required time [ms] for the animation with the given parameters
		 */
		static inline uint32_t speedToTime(uint32_t speed, int32_t start, int32_t end) noexcept { return lv_anim_speed_to_time(speed, start, end); }

		/**
		 * Manually refresh the state of the animations.
		 * Useful to make the animations running in a blocking process where
		 * `lv_timer_handler` can't run for a while.
		 * Shouldn't be used directly because it is called in `lv_refr_now()`.
		 */
		static inline void refrNow(void) noexcept { return lv_anim_refr_now(); }
	};

} /* namespace lvglpp */

#endif /* LVANIM_H_ */
